package com.xinghanlaser.applet.base.apilog.aspect;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xinghanlaser.applet.base.apilog.entity.ApiLogData;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;

import static com.xinghanlaser.applet.base.apilog.constant.ApiLogConstant.USER_NAME_HEADER;


/**
 * <p>
 *
 * <p>
 *
 * @author: mftuo 陀铭峰
 * @since: 2024/3/26 16:41
 */
@Aspect
@Order(99)
@Slf4j
public class ApiLogAspect {
    @Resource
    private ObjectMapper objectMapper;

    @Around("@annotation(apiOperation)")
    public Object around(ProceedingJoinPoint pjp, ApiOperation apiOperation) throws Throwable {
        // 获取请求信息
        ApiLogData apiLogData = Optional.ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .map(ServletRequestAttributes::getRequest)
                .filter(ObjectUtil::isNotNull)
                .map(request -> {
                    ApiLogData data = new ApiLogData();

                    // 请求哦用户信息
                    final String userName = request.getHeader(USER_NAME_HEADER);
                    if (StrUtil.isNotBlank(userName)) data.setUserName(userName);

                    // 接口描述信息
                    final String apiOperationDesc = apiOperation.value();
                    if (StrUtil.isNotBlank(apiOperationDesc)) data.setApiOperationDesc(apiOperationDesc);

                    // controller描述信息
                    putControllerDesc(pjp, data);

                    // 请求url
                    final String url = request.getRequestURL().toString();
                    if (StrUtil.isNotBlank(url)) data.setUrl(url);

                    // 请求IP
                    final String clientIp = getClientIp(request);
                    if(StrUtil.isNotBlank(clientIp)) data.setIp(clientIp);

                    // 请求方法
                    final String method = request.getMethod();
                    if (StrUtil.isNotBlank(method)) data.setMethod(method);

                    // 请求参数
                    try {
                        final String params = getParamJson(request, pjp);
                        if (StrUtil.isNotBlank(params)) data.setParams(params);
                    } catch (JsonProcessingException e) {
                        log.error("=== 请求参数转化错误 ===");
                    }
                    return data;
                }).orElse(new ApiLogData());


        final String uuid = UUID.randomUUID().toString();

        log.info("【{}】【======== 开始 {} ========】", uuid, apiLogData.getApiOperationDesc());
        log.info("【{}】【接口描述】: {}", uuid, apiLogData.getApiOperationDesc());
        log.info("【{}】【Controller描述】: {}", uuid, apiLogData.getControllerDesc());
        log.info("【{}】【请求IP】: {}", uuid, apiLogData.getIp());
        log.info("【{}】【UserName】: {}", uuid, apiLogData.getUserName());
        log.info("【{}】【请求URL】: {}", uuid, apiLogData.getUrl());
        log.info("【{}】【请求方法】: {}", uuid, apiLogData.getMethod());
        log.info("【{}】【请求参数】: {}", uuid, apiLogData.getParams());

        Object proceed = pjp.proceed();

        String description = apiOperation.value();
        String SEPARATOR = System.lineSeparator();
        log.info("【{}】【======== 结束 {} ========】{}",uuid, description, SEPARATOR);

        return proceed;
    }


    @AfterThrowing(value = "@annotation(apiOperation)", throwing = "throwable")
    public void afterThrow(JoinPoint joinPoint, ApiOperation apiOperation,Throwable throwable){
        String description = apiOperation.value();
        String SEPARATOR = System.lineSeparator();

        log.error(throwable.getMessage());
        log.info("============== 【异常】【结束 {}】 =============={}", description, SEPARATOR);
    }

    /**
     * 获取请求参数JSON
     */
    private String getParamJson(HttpServletRequest request, ProceedingJoinPoint pjp) throws JsonProcessingException {
        String requestType = request.getMethod();
        if ("GET".equals(requestType)) {
            // 如果是GET请求，直接返回QueryString
            return request.getQueryString();
        }

        // 可能有多个参数
        Object[] args = pjp.getArgs();
        Object[] arguments = new Object[args.length];

        for (int i = 0; i < args.length; i++) {
            // 只打印客户端传递的参数，排除Spring注入的参数
            if (args[i] instanceof ServletRequest
                    || args[i] instanceof ServletResponse
                    || args[i] instanceof MultipartFile
                    || args[i] instanceof MultipartFile[]
            ) {
                continue;
            }
            arguments[i] = args[i];
        }

        return objectMapper.writeValueAsString(arguments);
    }

    /**
     * 写入controller描述信息
     */
    private void putControllerDesc(ProceedingJoinPoint pjp, ApiLogData apiLogData){
        Optional.ofNullable((MethodSignature) pjp.getSignature())
                .map(signature -> (Class<?>) signature.getDeclaringType())
                .filter(controller -> controller.isAnnotationPresent(Api.class))
                .map(controller -> controller.getAnnotation(Api.class).value())
                .filter(StrUtil::isNotBlank)
                .ifPresent(apiLogData::setControllerDesc);
    }

    /**
     * 获取客户端IP
     */
    private String getClientIp(HttpServletRequest request) {
        //使用网关过滤器重写请求头
        final String clientIp = request.getHeader("clientIp");
        if (!StringUtils.isEmpty(clientIp)){
            return clientIp;
        }
        // 如果有代理转发，真实的ip会放在X-Forwarded-For
        String xff = request.getHeader("X-Forwarded-For");
        if (xff == null) {
            return request.getRemoteAddr();
        } else {
            return xff.contains(",") ? xff.split(",")[0] : xff;
        }
    }
}
